package org.lex.game.sudoku.view.solver;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JSplitPane;
import javax.swing.SwingWorker;
import javax.swing.border.TitledBorder;

import org.lex.game.sudoku.datatype.Result;
import org.lex.game.sudoku.model.DefaultSudokuModel;
import org.lex.game.sudoku.model.SudokuModel;
import org.lex.game.sudoku.model.builder.IntArraySudokuBuilder;
import org.lex.game.sudoku.model.solver.Decision;
import org.lex.game.sudoku.model.solver.DefaultSolver;
import org.lex.game.sudoku.model.solver.SolverObserver;
import org.lex.game.sudoku.model.visitor.CopyModelVisitor;
import org.lex.game.sudoku.model.visitor.ModelVisitors;
import org.lex.game.sudoku.util.SolverObserverList;

public class SolverObserverDialog extends JDialog implements SolverObserver {
	public static void main(String[] args) {
		SolverObserverDialog d = new SolverObserverDialog(null, new DefaultSudokuModel());
		d.setLocationRelativeTo(null);
		d.setVisible(true);
	}

	private static final long serialVersionUID = -2233023925585007300L;

	private static final int DEFAULT_SPEED = 500;

	private JPanel contentPanel;
	private JPanel controlPane;
	private JLabel speedLabel;
	private JSlider speedSlider;
	private JPanel buttonsPane;
	private JCheckBox fastModeCheckBox;
	private JPanel buttonPane;
	private JButton stopButton;
	private JButton startButton;

	private JPanel observerPane;
	private LabelSolverObserver decisionsLabel;
	private ProgressBarSolverObserver progressBar;
	private JSplitPane splitPane;
	private TextAreaSolverObserver textArea = null;
	private TreeSolverObserver tree;

	private final SudokuModel model;
	private SwingWorker<Result, Void> lastRun = null;
	private SolverObserverList observerList = null;

	public SolverObserverDialog(Window root, SudokuModel model) {
		super(root);
		this.model = model;
		initialize();
		this.getProgressBar().setSudokuModel(model);
	}

	void start() {
		this.reset();
		this.getStartButton().setEnabled(false);
		this.getFastModeCheckBox().setEnabled(false);
		this.getSpeedSlider().setEnabled(false);
		this.getStopButton().setEnabled(true);

		int speed = this.getSpeedSlider().getValue();
		if (this.getFastModeCheckBox().isSelected()) {
			speed = 0;
		}
		final int finalSpeed = speed;
		final SudokuModel finalModel = this.getRunningModel();
		this.getProgressBar().setSudokuModel(finalModel);

		this.lastRun = new SwingWorker<Result, Void>() {
			@Override
			protected Result doInBackground() throws Exception {
				DefaultSolver solver = new DefaultSolver();
				solver.setDelay(finalSpeed);
				// solver.setDebug(false);
				return solver.solve(finalModel, SolverObserverDialog.this);
			}

			@Override
			protected void done() {
				getStartButton().setEnabled(true);
				getFastModeCheckBox().setEnabled(true);
				getSpeedSlider().setEnabled(!getFastModeCheckBox().isSelected());
				getStopButton().setEnabled(false);
				// if using fast mode, copy to original model
				if (getFastModeCheckBox().isSelected()) {
					ModelVisitors.visit(finalModel, new CopyModelVisitor(SolverObserverDialog.this.model));
				}
			}
		};
		this.lastRun.execute();
	}

	void stop() {
		if (null != this.lastRun) {
			this.lastRun.cancel(true);
			this.lastRun = null;
			getStartButton().setEnabled(true);
			getFastModeCheckBox().setEnabled(true);
			getSpeedSlider().setEnabled(!getFastModeCheckBox().isSelected());
			getStopButton().setEnabled(false);
		}
	}

	SudokuModel getRunningModel() {
		SudokuModel model = this.model;
		if (this.getFastModeCheckBox().isSelected()) {
			int[][] data = model.toArray();
			SudokuModel copy = new DefaultSudokuModel();
			copy.newGame(new IntArraySudokuBuilder(data));
			model = copy;
		}
		return model;
	}

	@Override
	public void beginDecision(Decision decision) {
		this.getObserverList().beginDecision(decision);
	}

	@Override
	public void fillCell(int row, int col, int num) {
		this.getObserverList().fillCell(row, col, num);
	}

	@Override
	public void rollbackDecision(Decision decision, int cellCount) {
		this.getObserverList().rollbackDecision(decision, cellCount);
	}

	@Override
	public void reset() {
		this.getObserverList().reset();
	}

	private SolverObserverList getObserverList() {
		if (null == this.observerList) {
			this.observerList = new SolverObserverList();
			this.observerList.add(this.getDecisionsLabel());
			this.observerList.add(this.getProgressBar());
			this.observerList.add(this.getTextArea());
			this.observerList.add(this.getTree());
		}
		return observerList;
	}

	private void initialize() {
		this.setContentPane(this.getContentPanel());
		this.setTitle("Auto Solve");
		this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
		this.setSize(700, 560);
	}

	private JPanel getContentPanel() {
		if (null == contentPanel) {
			contentPanel = new JPanel(new BorderLayout(0, 0));
			contentPanel.add(this.getControlPane(), BorderLayout.NORTH);
			contentPanel.add(this.getObserverPane(), BorderLayout.CENTER);
		}
		return contentPanel;
	}

	private JPanel getControlPane() {
		if (controlPane == null) {
			controlPane = new JPanel();
			GridBagLayout layout = new GridBagLayout();
			controlPane.setLayout(layout);
			controlPane.setBorder(new TitledBorder(null, "Control", TitledBorder.CENTER, TitledBorder.TOP, null, null));

			GridBagConstraints speedLabelConstraints = new GridBagConstraints();
			speedLabelConstraints.insets = new Insets(0, 0, 5, 5);
			speedLabelConstraints.anchor = GridBagConstraints.PAGE_START;
			speedLabelConstraints.gridx = 0;
			speedLabelConstraints.gridy = 0;
			controlPane.add(getSpeedLabel(), speedLabelConstraints);

			GridBagConstraints speedSliderConstraints = new GridBagConstraints();
			speedSliderConstraints.weightx = 10.0;
			speedSliderConstraints.insets = new Insets(0, 5, 5, 0);
			speedSliderConstraints.anchor = GridBagConstraints.BASELINE_LEADING;
			speedSliderConstraints.fill = GridBagConstraints.HORIZONTAL;
			speedSliderConstraints.gridx = 1;
			speedSliderConstraints.gridy = 0;
			controlPane.add(getSpeedSlider(), speedSliderConstraints);

			GridBagConstraints buttonsPaneConstraints = new GridBagConstraints();
			buttonsPaneConstraints.gridwidth = 2;
			buttonsPaneConstraints.weightx = 10.0;
			buttonsPaneConstraints.anchor = GridBagConstraints.PAGE_START;
			buttonsPaneConstraints.insets = new Insets(0, 0, 5, 0);
			buttonsPaneConstraints.fill = GridBagConstraints.HORIZONTAL;
			buttonsPaneConstraints.gridx = 0;
			buttonsPaneConstraints.gridy = 1;
			controlPane.add(getButtonsPane(), buttonsPaneConstraints);
		}
		return controlPane;
	}

	private JLabel getSpeedLabel() {
		if (speedLabel == null) {
			speedLabel = new JLabel("Speed");
			speedLabel.setLabelFor(getSpeedSlider());
		}
		return speedLabel;
	}

	private JSlider getSpeedSlider() {
		if (speedSlider == null) {
			speedSlider = new JSlider();
			speedSlider.setSnapToTicks(true);
			speedSlider.setPaintLabels(true);
			speedSlider.setPaintTicks(true);
			speedSlider.setMinorTickSpacing(100);
			speedSlider.setMajorTickSpacing(500);
			speedSlider.setMaximum(2000);
			speedSlider.setValue(DEFAULT_SPEED);
		}
		return speedSlider;
	}

	private JPanel getButtonsPane() {
		if (buttonsPane == null) {
			buttonsPane = new JPanel(new BorderLayout(0, 0));
			buttonsPane.add(getFastModeCheckBox(), BorderLayout.WEST);
			buttonsPane.add(getButtonPane(), BorderLayout.CENTER);
		}
		return buttonsPane;
	}

	private JCheckBox getFastModeCheckBox() {
		if (fastModeCheckBox == null) {
			fastModeCheckBox = new JCheckBox("Fast mode", false);
			fastModeCheckBox.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					boolean selected = fastModeCheckBox.isSelected();
					getSpeedSlider().setEnabled(!selected);
				}
			});
		}
		return fastModeCheckBox;
	}

	private JPanel getButtonPane() {
		if (buttonPane == null) {
			FlowLayout layout = new FlowLayout(FlowLayout.CENTER, 11, 0);
			layout.setAlignOnBaseline(true);

			buttonPane = new JPanel(layout);
			buttonPane.add(getStartButton());
			buttonPane.add(getStopButton());
		}
		return buttonPane;
	}

	private JButton getStopButton() {
		if (stopButton == null) {
			stopButton = new JButton("Stop");
			stopButton.setEnabled(false);
			stopButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					stop();
				}
			});
		}
		return stopButton;
	}

	private JButton getStartButton() {
		if (startButton == null) {
			startButton = new JButton("Start");
			startButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					start();
				}
			});
		}
		return startButton;
	}

	private JPanel getObserverPane() {
		if (null == this.observerPane) {
			JPanel pane = new JPanel(new GridLayout(2, 1, 0, 0));
			pane.add(this.getDecisionsLabel());
			pane.add(this.getProgressBar());

			observerPane = new JPanel(new BorderLayout(0, 0));
			observerPane.add(pane, BorderLayout.NORTH);
			observerPane.add(getSplitPane(), BorderLayout.CENTER);
		}
		return this.observerPane;
	}

	private ProgressBarSolverObserver getProgressBar() {
		if (null == progressBar) {
			progressBar = new ProgressBarSolverObserver();
		}
		return progressBar;
	}

	private LabelSolverObserver getDecisionsLabel() {
		if (decisionsLabel == null) {
			decisionsLabel = new LabelSolverObserver();
		}
		return decisionsLabel;
	}

	private JSplitPane getSplitPane() {
		if (splitPane == null) {
			splitPane = new JSplitPane();
			splitPane.setDividerSize(6);
			splitPane.setLeftComponent(new JScrollPane(this.getTextArea()));
			splitPane.setRightComponent(new JScrollPane(this.getTree()));
			splitPane.setResizeWeight(0.5);
		}
		return splitPane;
	}

	private TextAreaSolverObserver getTextArea() {
		if (null == this.textArea) {
			this.textArea = new TextAreaSolverObserver();
		}
		return this.textArea;
	}

	private TreeSolverObserver getTree() {
		if (tree == null) {
			tree = new TreeSolverObserver();
		}
		return tree;
	}
}
